package fabricsdk_go

import (
	"context"
	"crypto/tls"
	"fmt"
	"github.com/hyperledger/fabric/core/comm"
	cb "github.com/hyperledger/fabric/protos/common"
	ab "github.com/hyperledger/fabric/protos/orderer"
	"github.com/pkg/errors"
	"time"
)

type OrdererConfig struct {
	Host string
	ServiceOverrideName string
	RootTlsCert []byte
}

func (conf OrdererConfig) client() (*ordererClient, error) {
	clientConfig := comm.ClientConfig{}
	secOpts := &comm.SecureOptions{}
	if conf.RootTlsCert != nil {
		secOpts.UseTLS = true
		secOpts.ServerRootCAs = [][]byte{conf.RootTlsCert}
	}
	clientConfig.Timeout = 3 * time.Second
	clientConfig.SecOpts = secOpts
	gClient, err := comm.NewGRPCClient(clientConfig)
	if err != nil {
		return nil, err
	}
	return &ordererClient{
		commonClient: commonClient{
			GRPCClient: gClient,
			sn: conf.ServiceOverrideName,
			address: conf.Host,
		},
	}, nil
}

type ordererClient struct {
	commonClient
}

// Broadcast returns a broadcast client for the AtomicBroadcast service
func (oc *ordererClient) Broadcast() (ab.AtomicBroadcast_BroadcastClient, error) {
	conn, err := oc.commonClient.NewConnection(oc.address, oc.sn)
	if err != nil {
		return nil, errors.WithMessage(err, fmt.Sprintf("orderer client failed to connect to %s", oc.address))
	}
	// TODO: check to see if we should actually handle error before returning
	return ab.NewAtomicBroadcastClient(conn).Broadcast(context.TODO())
}

// Deliver returns a deliver client for the AtomicBroadcast service
func (oc *ordererClient) Deliver() (ab.AtomicBroadcast_DeliverClient, error) {
	conn, err := oc.commonClient.NewConnection(oc.address, oc.sn)
	if err != nil {
		return nil, errors.WithMessage(err, fmt.Sprintf("orderer client failed to connect to %s", oc.address))
	}
	// TODO: check to see if we should actually handle error before returning
	return ab.NewAtomicBroadcastClient(conn).Deliver(context.TODO())
}

func (oc *ordererClient) GetBroadcastClient() (BroadcastClient, error) {
	bcli, err := oc.Broadcast()
	if err != nil {
		return nil, err
	}
	return &broadcastClient{
		client: bcli,
	}, nil
}

// Certificate returns the TLS client certificate (if available)
func (oc *ordererClient) Certificate() tls.Certificate {
	return oc.commonClient.Certificate()
}

type BroadcastClient interface {
	//Send data to orderer
	Send(env *cb.Envelope) error
	Close() error
}

type broadcastClient struct {
	client ab.AtomicBroadcast_BroadcastClient
}

func (bc *broadcastClient) getAck() error {
	msg, err := bc.client.Recv()
	if err != nil {
		return err
	}
	if msg.Status != cb.Status_SUCCESS {
		return errors.Errorf("got unexpected status: %v -- %s", msg.Status, msg.Info)
	}
	return nil
}

func (bc *broadcastClient) Send(env *cb.Envelope) error {
	err := bc.client.Send(env)
	if err != nil {
		return errors.WithMessage(err, "could not send")
	}
	err = bc.getAck()
	return err
}

func (bc *broadcastClient) Close() error {
	return bc.client.CloseSend()
}

